package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.util.JsonUtils;
import com.leyou.common.util.NumberUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repositiry.GoodsRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SearchService {
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    public Goods buildGoods(Spu spu){
        Goods goods = new Goods();
        try {
            Long spuId = spu.getId();

            //查询分类
            List<Category> categories = categoryClient.queryCategoryByIds(
                    Arrays.asList(spu.getCid1(),
                            spu.getCid2(),
                            spu.getCid3()));
            if (CollectionUtils.isEmpty(categories)) {
                throw new LyException(ExceptionEnum.CATEGORY_NOT_FOUD);
            }

            List<String> names = categories.stream().map(Category::getName).collect(Collectors.toList());


            //查询品牌
            Brand brand = brandClient.queryBrandById(spu.getBrandId());

            if (brand == null) {
                throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
            }

            //拼接所有的搜索字段
            String all = spu.getTitle() + StringUtils.join(names, " ") + brand.getName();

            //查询Sku
            List<Sku> skuList = goodsClient.querySkuBySpuId(spuId);

            if (CollectionUtils.isEmpty(skuList)) {
                throw new LyException(ExceptionEnum.GOODS_SKU_NOT_FOND);
            }

            //查询出的skuList中的Sku不要用属性太多，所以单独处理
            List<Map<String, Object>> skus = new ArrayList<>();
            Set<Long> priceList = new HashSet<>();
            for (Sku sku : skuList) {
                Map<String, Object> skuMapp = new HashMap<>();
                skuMapp.put("id", sku.getId());
                skuMapp.put("title", sku.getTitle());
                skuMapp.put("price", sku.getPrice());
                skuMapp.put("image", StringUtils.substringBefore(sku.getImages(), ","));

               // System.out.println("map:" + skuMapp);
                skus.add(skuMapp);
                //封装所以价格的集合
                priceList.add(sku.getPrice());
            }
            //查询规格参数
            List<SpecParam> params = specificationClient.queryParamList(null, spu.getCid3(), true);
            if (CollectionUtils.isEmpty(params)) {
                throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOND);
            }
            //查询商品详情
            SpuDetail spuDetail = goodsClient.queryDetailById(spuId);

            //System.out.println("spuDetail.getGenericSpec():" + spuDetail.getGenericSpec());
            //获得通用规格参数
            Map<Long, String> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, String.class);

            //获得特有规格参数
            Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
            });


            //规格参数 key是规格参数的名字 值是规格参数的值
            Map<String, Object> specs = new HashMap<>();
            for (SpecParam param : params) {
                //规则名称
                String key = param.getName();

                //规格的值。
                //判断是否是通用规格参数
                Object value = "";
                if (param.getGeneric()) {
                    value = genericSpec.get(param.getId());
                    //判断是否是数字类型
                    if (param.getNumeric()) {
                        //处理成段
                        value = chooseSegment(value.toString(), param);
                    }
                } else {
                    value = specialSpec.get(param.getId());
                }
                //存入map集合
                specs.put(key, value);

            }


            //构建goods对象

            goods.setBrandId(spu.getBrandId());
            goods.setCid1(spu.getCid1());
            goods.setCid2(spu.getCid2());
            goods.setCid3(spu.getCid3());
            goods.setCreateTime(spu.getCreateTime());

            goods.setId(spuId);
            goods.setAll(all);//TODO 搜索字段 包含标题 分类 品牌 规格
            goods.setPrice(priceList);//TODO 所有sku的价格集合
            goods.setSkus(JsonUtils.toString(skus));//TODO 所有sku的集合的json格式
            goods.setSpecs(specs);//TODO 所有的看搜索的规格参数
            goods.setSubTitle(spu.getSubTitle());
        }catch (Exception e){
           // System.err.println("spuId:" + spu.getId());
            e.printStackTrace();
        }

        return  goods;
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public PageResult<Goods> search(SearchRequest request) {
        //创建查询构建起
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //0。结果列的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new  String[]{"id","subTitle","skus"},null));

        //1。分页  es是从0页开始的
        Integer page = request.getPage() - 1;
        Integer size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page,size));
        //2过滤
        QueryBuilder basicQuery = buildBasicQuery(request);// QueryBuilders.matchQuery("all", request.getKey());
        queryBuilder.withQuery(basicQuery);

        //3聚合

        //3.1集合分类
        String categoryAggName = "category_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //3.2集合品牌
        String  brandAggName ="brand_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //4查询
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(),Goods.class);


        //5解析结果
        //5.1解析分页结果
        long total = result.getTotalElements();
        long totalPages = result.getTotalPages();
        List<Goods> goodsList = result.getContent();

        //5.2解析集合结果
        Aggregations aggs = result.getAggregations();
        List<Category> categories = parseCategoryAgg(aggs.get(categoryAggName));
        List<Brand> brands = parseBrandAgg(aggs.get(brandAggName));

        List<Map<String,Object>> specs = null;

        //6.完成规格参数集合
        //判断分类待选项中是否只有一个带选项
        if(categories != null && categories.size() == 1){
            specs = buildSpecificationAgg(categories.get(0).getId(),basicQuery);
        }

        return new SearchResult(total,totalPages,goodsList,categories,brands,specs);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();


        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()));


        Map<String, String> filter = request.getFilterMap();


        Set<Map.Entry<String, String>> entries = filter.entrySet();

        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            if(!"cid3".equals(key) && !"brand".equals(key)){
                key = "specs." +key +".keyword";
            }
            queryBuilder.filter(QueryBuilders.termQuery(key,value));

        }

        return  queryBuilder;
    }

    /**
     *
     * @param cid
     * @param basicQuery
     * @return
     */
    private List<Map<String,Object>> buildSpecificationAgg(Long cid, QueryBuilder basicQuery) {
        List<Map<String,Object>> specs = new ArrayList<>();

        //1.根据分类的id，查询该分类下有哪些规格参数
        List<SpecParam> params = specificationClient.queryParamList(null, cid, true);
        //2. 按照规格参数进行聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1 带上查询条件
        queryBuilder.withQuery(basicQuery);
        //2.2 集合  每个规格都进行聚合
        for (SpecParam param : params) {
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        }

        //3获得聚合的结果
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4.解析结果
        Aggregations aggs = result.getAggregations();
        for (SpecParam param : params) {
            String name = param.getName();
            StringTerms terms = aggs.get(name);
            Map<String,Object> map = new HashMap<>();
            map.put("k",name);
            map.put("options",terms.getBuckets().stream()
                    .map(b -> b.getKeyAsString()).collect(Collectors.toList()));
            specs.add(map);
        }
        return specs;
    }

    /**
     * 解析品牌的聚合方法
     * @param terms
     * @return
     */
    private List<Brand> parseBrandAgg(LongTerms terms) {
        try {
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());

            List<Brand> brands = brandClient.queryBrandByIds(ids);
            return brands;
        }catch (Exception e){
            log.error("[搜索服务] 查询品牌异常：" ,e);
            return null;
        }
    }

    /**
     * 解析分析的聚合方法
     * @param terms
     * @return
     */
    private List<Category> parseCategoryAgg(LongTerms terms) {
          try {
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Category> categoryList = categoryClient.queryCategoryByIds(ids);
            return categoryList;
        }catch (Exception e){
              log.error("[搜索服务] 查询分类异常：",e);
            return null;
        }
    }

    /**
     * 新增或者修改索引库
     * @param spuId
     */
    public void createOuUpdateIndex(Long spuId) {
        //查询Goods
        Spu spu = goodsClient.querySpuById(spuId);
        //构建goods
        Goods goods = buildGoods(spu);
        //存入索引库
        goodsRepository.save(goods);
    }

    /**
     * 删除索引库中的所以
     * @param spuId
     */
    public void deleteIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
